package dm.bsi;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;

import org.dom4j.DocumentException;
import org.junit.experimental.theories.Theories;

import indi.zjh.dp.db.Session;
import indi.zjh.dp.error.UnsuportedFieldException;
import indi.zjh.dp.orm.Business;
import indi.zjh.dp.orm.Query;
import indi.zjh.dp.orm.Relation;
import indi.zjh.dp.orm.Result;
import indi.zjh.dp.type.String256;
import indi.zjh.dp.util.SystemCmd;


public class Backup extends Business {
	
	protected String256 filepath = new String256("undefined");
	protected String256 basefilename = new String256("undefined");
	protected String256 dirname = new String256("undefined");
	protected Relation service = new Relation(Service.class, Relation.ONE);
	protected Relation node = new Relation(Node.class, Relation.ONE);
	
	protected Relation container = new Relation(Container.class); // 用来保存备份时当时的master容器
	
	/**
	 * 获取关联的Node对象
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public Node getNode(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.node.size() == 0)
			return null;
		long nodeid = this.node.getIds()[0];
		Node node = (Node)session.getById(Node.class, nodeid);
		return node;
	}
	
	/**
	 * 获取关联的容器
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public Container getContainer(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.container.size() == 0)
			return null;
		long id = this.container.getIds()[0];
		Container obj = (Container)session.getById(Container.class, id);
		return obj;
	}
	
	/**
	 * 获取关联的Service对象
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public Service getService(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.service.size() == 0)
			return null;
		long id = this.service.getIds()[0];
		Service obj = (Service)session.getById(Service.class, id);
		return obj;
	}
	
	/**
	 * 备份数据
	 * @param service
	 * @param node
	 * @throws Exception 
	 * @throws DocumentException 
	 * @throws UnsuportedFieldException 
	 * @throws ClassNotFoundException 
	 */
	public void saveData(Session session, Service service, Container container) throws ClassNotFoundException, UnsuportedFieldException, DocumentException, Exception
	{
		String cmd = null;
		String response = null;
		// 已经备份过
		if(!this.filepath.string.equals("undefined"))
		{
			return;
		}
		
		this.service.add(service);
		Node node = container.getNode(session);
		this.node.add(node);
		
		service.getBackups().add(this);
		node.getBackups().add(this);
		
		
		Deployment deployment = service.getDeployment(session);
		Log.info(session, deployment, "准备备份"+service.getName()+"数据");
		
		if(service.getCatalog() == Service.CATELOG_MYSQL)
		{
			SimpleDateFormat df = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
			String dirname = df.format(new Date());
			
			// 创建备份目录
			cmd = "ssh root@"+node.getIp()+" 'docker exec -i "+container.getOid()+" mkdir -p /"+dirname+";echo FINISH'";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, deployment, cmd+":"+response);
			
			// 备份
			cmd = "ssh root@"+node.getIp()+" 'docker exec -i "+container.getOid()+" bash -c \"innobackupex --defaults-file=/etc/mysql/my.cnf --user=root --password=123456 --slave-info --no-timestamp /"+dirname+" 2>&1|grep -c \\\"completed OK!\\\"\"'";
			Log.info(session, deployment, cmd);
			response = SystemCmd.execCMD(cmd, 3600).trim();
			Log.info(session, deployment, cmd+":"+response);
			
			if(!response.equals("2"))
			{
				Log.failure(session, deployment, "备份失败");
				return;
			}
			
			// 备份应用
			cmd = "ssh root@"+node.getIp()+" 'docker exec -i "+container.getOid()+" bash -c \"innobackupex --defaults-file=/etc/mysql/my.cnf --user=root --password=123456 --apply-log --user-memory=1GB /"+dirname+" 2>&1|grep -c \\\"completed OK!\\\"\"'";
			Log.info(session, deployment, cmd);
			response = SystemCmd.execCMD(cmd, 300).trim();
			Log.info(session, deployment, cmd+":"+response);
			if(!response.equals("2"))
			{
				Log.failure(session, deployment, "备份apply-log失败");
				return;
			}
			
			// 备份打包
			cmd = "ssh root@"+node.getIp()+" 'docker exec -i "+container.getOid()+" bash -c \"tar czf "+dirname+".tgz "+dirname+";echo FINISH\"'";
			Log.info(session, deployment, cmd);
			response = SystemCmd.execCMD(cmd, 300);
			Log.info(session, deployment, cmd+":"+response);
			
			// 删除备份目录
			cmd = "ssh root@"+node.getIp()+" 'docker exec -i "+container.getOid()+" bash -c \"rm -rf /"+dirname+";echo FINISH\"'";
			response = SystemCmd.execCMD(cmd, 30);
			Log.info(session, deployment, cmd+":"+response);
			
			// 创建保存备份目录
			cmd = "ssh root@"+node.getIp()+" 'mkdir -p /var/backup/"+service.getName()+"/';echo FINISH";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, deployment, cmd+":"+response);
			
			// 移动备份打包
			cmd = "ssh root@"+node.getIp()+" 'docker cp "+container.getOid()+":/"+dirname+".tgz /var/backup/"+service.getName()+"/;echo FINISH'";
			response = SystemCmd.execCMD(cmd, 300);
			Log.info(session, deployment, cmd+":"+response);
			
			// 删除容器内备份打包备份
			cmd = "ssh root@"+node.getIp()+" 'docker exec -i "+container.getOid()+" bash -c \"rm -rf /"+dirname+".tgz;echo FINISH\"'";
			response = SystemCmd.execCMD(cmd, 30);
			Log.info(session, deployment, cmd+":"+response);
			
			service.setBackup(false);
			this.filepath.string = "/var/backup/"+service.getName()+"/"+dirname+".tgz";
			this.basefilename.string = dirname+".tgz";
			this.dirname.string = dirname;
		}
		else
		{
			throw new Exception("不支持"+service.getName()+"数据备份");
		}

		Log.info(session, deployment, "成功备份"+service.getName()+"数据");
		session.save(service).save(node).save(this).commit();
	}
	
	public static Backup getBackup(Session session, Service service, Container currentMaster) throws SQLException, DocumentException, UnsuportedFieldException, Exception
	{
		Query<Backup> query = new Query<Backup>(session);
		
		Result<Backup> result = null;
		// 找到当前master容器一样的备份
		if(currentMaster != null)
		{
			result = query.find(Backup.class).association(Backup.class, Service.class, "`_id` = "+service.getId()).association(Backup.class, Container.class, "`_id` = "+currentMaster.getId()).order(Backup.class, "`_id` DESC").limit(0, 3).result();
		}
		else
		{
			result = query.find(Backup.class).association(Backup.class, Service.class, "`_id` = "+service.getId()).association(Backup.class, Container.class).order(Backup.class, "`_id` DESC").limit(0, 3).result();
		}
		Deployment deployment = service.getDeployment(session);
		Log.info(session, deployment, "SQL:"+query.getLastQuerySql());
		
		// 没有任何备份
		if(result.count() == 0)
			return null;
		
		Iterator<Backup> iter = result.iterator();
		Backup backup = null;
		String cmd = null;
		String response = null;
		boolean isFind = false;
		while(iter.hasNext())
		{
			backup = iter.next();
			
			// 判断备份文件还在不在
			Node node = backup.getNode(session);
			cmd = "ssh root@"+node.getIp()+" 'ls "+backup.getFilepath()+"'";
			response = SystemCmd.execCMD(cmd, 10).trim();
			if(response.isEmpty())
			{
				continue;
			}
			
			isFind = true;
			break;
		}
		if(isFind)
			return backup;
		return null;
	}
	
	public HashMap<String, String> restoreData(Session session, Container newContainer) throws UnsuportedFieldException, DocumentException, Exception
	{
		HashMap<String, String> result = new HashMap<String, String>();
		if(this.node.size() == 0 || this.service.size() == 0 || this.container.size() == 0)
			return null;
		
		Service service = this.getService(session);
		Node node = this.getNode(session);
		Container container = this.getContainer(session);
		Node newNode = newContainer.getNode(session);
		Deployment deployment = service.getDeployment(session);
		String cmd = null;
		String response = null;
		
		if(service.getCatalog() == Service.CATELOG_MYSQL)
		{
			result.put("MASTER_HOST", node.getIp());
			result.put("MASTER_PORT", container.getDockerPort()+"");
			result.put("MASTER_USER", "repl");
			result.put("MASTER_PASSWORD", "replpwd");
			// MASTER_LOG_FILE, MASTER_LOG_POS
			
			// 拷贝备份数据文件到本地
			cmd = "scp root@"+node.getIp()+":"+this.filepath+" /var/";
			response = SystemCmd.execCMD(cmd, 600);
			Log.info(session, deployment, cmd+":"+response);
			
			// 拷贝备份数据到新服务器节点
			cmd = "scp /var/"+this.basefilename+" root@"+newNode.getIp()+":/var/";
			response = SystemCmd.execCMD(cmd, 600);
			Log.info(session, deployment, cmd+":"+response);
			
			// 拷贝至容器内
			cmd = "ssh root@"+newNode.getIp()+" 'docker cp /var/"+this.basefilename+" "+newContainer.getOid()+":/;echo FINISH'";
			response = SystemCmd.execCMD(cmd, 600);
			Log.info(session, deployment, cmd+":"+response);
			
			// 在容器内解压
			cmd = "ssh root@"+newNode.getIp()+" 'docker exec -i "+newContainer.getOid()+" bash -c \"tar zxf ./"+this.basefilename+"\"'";
			response = SystemCmd.execCMD(cmd, 600);
			Log.info(session, deployment, cmd+":"+response);
			
			// 删除旧的数据库目录
			cmd = "ssh root@"+newNode.getIp()+" 'docker exec -i "+newContainer.getOid()+" bash -c \"rm -rf /var/lib/mysql/*;echo FINISH\"'";
			response = SystemCmd.execCMD(cmd, 30);
			Log.info(session, deployment, cmd+":"+response);
			
			// 恢复数据
			cmd = "ssh root@"+newNode.getIp()+" 'docker exec -i "+newContainer.getOid()+" bash -c \"innobackupex --defaults-file=/etc/mysql/my.cnf --user=root --password=123456 --copy-back /"+this.dirname+" 2>&1|grep -c \\\"completed OK!\\\"\"'"; 
			response = SystemCmd.execCMD(cmd, 600).trim();
			Log.info(session, deployment, cmd+":"+response);
			if(!response.equals("2"))
			{
				Log.failure(session, deployment, "恢复数据失败");
				return null;
			}
			
			
			// 修改权限
			cmd = "ssh root@"+newNode.getIp()+" 'docker exec -i "+newContainer.getOid()+" bash -c \"chmod -R 755 /var/lib/mysql;chown mysql:mysql -R /var/lib/mysql;echo FINISH\"'";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, deployment, cmd+":"+response);
			
			// 重启容器
//			cmd = "ssh root@"+newNode.getIp()+" 'docker restart "+newContainer.getOid()+";echo FINISH'";
//			response = SystemCmd.execCMD(cmd, 30);
//			Log.info(session, deployment, cmd+":"+response);
			Log.info(session, deployment, "容器旧接口dockerPort="+newContainer.getDockerPort());
			newContainer.restart(session);
			
			Container tmpCon = (Container)session.getById(Container.class, newContainer.getId());
			Log.info(session, deployment, "容器新接口dockerPort="+tmpCon.getDockerPort());
			
			// 获取新的同步位置
			cmd = "ssh root@"+newNode.getIp()+" 'docker exec -i "+newContainer.getOid()+" bash -c \"cat /"+this.dirname+"/xtrabackup_slave_info\"'";
			response = SystemCmd.execCMD(cmd, 30);
			Log.info(session, deployment, cmd+":"+response);
			
			//CHANGE MASTER TO MASTER_LOG_FILE='mysql-1-bin.000003', MASTER_LOG_POS=637;
			
			String[] strs1 = response.split(",");
			String[] strs2 = strs1[0].split("'");
			result.put("MASTER_LOG_FILE", strs2[1].trim());
			String[] strs3 = strs1[1].split("=");
			String[] strs4 = strs3[1].split(";");
			result.put("MASTER_LOG_POS", strs4[0].trim());
		}
		else
		{
			return null;
		}
		
		return result;
	}
	public static String toDictString(Method m)
	{
		if(m.getName().equals("getNode"))
		{
			return "获取备份存储的目标节点";
		}
		else if(m.getName().equals("getContainer"))
		{
			return "获取备份产生时的master容器";
		}
		else if(m.getName().equals("getService"))
		{
			return "获取产生备份的服务";
		}
		else if(m.getName().equals("saveData"))
		{
			return "生成备份数据";
		}
		else if(m.getName().equals("getBackup"))
		{
			return "获取备份数据";
		}
		else if(m.getName().equals("restoreData"))
		{
			return "恢复数据备份";
		}
		return Business.toDictString(m);
	}
	
	public static String toDictString(Field f)
	{
		if(f.getName().equals("filepath"))
		{
			return "备份文件的完整路径, 目录+文件名";
		}
		else if(f.getName().equals("basefilename"))
		{
			return "备份文件的文件名和后缀";
		}
		else if(f.getName().equals("dirname"))
		{
			return "备份文件解压后目录名称";
		}
		else if(f.getName().equals("service"))
		{
			return "关联的服务";
		}
		else if(f.getName().equals("node"))
		{
			return "关联的节点, 备份的物理节点";
		}
		return Business.toDictString(f);

	}
	
	
	

	public Relation getContainer() {
		return container;
	}

	public String getFilepath() {
		return filepath.string;
	}

	public void setFilepath(String filepath) {
		this.filepath.string = filepath;
	}

	public Relation getService() {
		return service;
	}


	public Relation getNode() {
		return node;
	}

	@Override
	public String toString() {
		return "Backup [filepath=" + filepath + ", service=" + service + ", node=" + node + ", container=" + container
				+ ", _id=" + _id + ", _v=" + _v + "]";
	}
	
	
}
